package com.pan.download5.ctrl;

import com.pan.download5.config.StoryWriterConfig;
import com.pan.download5.sync.CasUnlockAndBlock;
import com.pan.download5.sync.UnlockAndBlock;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

/**
 * 这是重新章节同步写入器的重新实现
 */
public class ChapterSyncWriterImpl2 implements ChapterSyncWriter,StoryWriterConfig {

//  放弃了原先内部类的实现办法，本类在同步章节的过程中已经确保了写入字符流的线程安全，因此字符流的没必要过度封装
//    private ChapterWriter writer ;


    private FileWriter writer;
    public ChapterSyncWriterImpl2(String storyName) {
        String outputDir = this.getOutputDir();
        boolean append = this.getWriteAppend();
        File dir = new File(outputDir);
        if (!dir.exists()) dir.mkdir();
        try {
            this.writer = new FileWriter(outputDir+storyName+".txt",append);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public void close() {
        try {
            writer.flush();
            writer.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        unlockAndBlock.close();
    }

    @Override
    public void writeChapter(int chapterId, String content) {
        writeWait(chapterId);
        try {
            writer.write(content);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        notificationCompleted(chapterId);
    }


    private volatile int completeId = -1;
    private final int SPIN_LIMIT = 0x11451419;
    private UnlockAndBlock unlockAndBlock = new CasUnlockAndBlock();

    private void writeWait(int chapterId) {
        int previousId = chapterId - 1;//章节id从0开始
        for (;;) {
            for (int i = 0; i < this.SPIN_LIMIT; i++)
                if (previousId == this.completeId)
                    return;
            writePark(chapterId);//唤醒后还要再次判断是否符合条件
        }
    }

    /**
     * writePark 和 notificationCompleted 是互斥的
     * 1. 两个方法在竞争同一把锁
     * 2. 两个方法实质是共同操作线程挂起的状态
     */
    private void writePark(int chapterId) {
        int previousId = chapterId - 1;
        unlockAndBlock.lock();
        //拿到锁后再次判断是否前一个任务是否完成
        if (previousId == completeId) {
            try {//如果完成就返回放行并释放锁
                return;
            } finally {
                unlockAndBlock.unlock();
            }
        }
        //如果未完成，则阻塞线程等待，让前面的任务完成后再唤醒
        unlockAndBlock.unlockAndBlock(chapterId);
    }

    private void notificationCompleted(int chapterId) {
        unlockAndBlock.lock();
        //拿到锁才能修改completeId
        completeId = chapterId;
        //修改完就可以唤醒下一个线程了
        unlockAndBlock.waitBlockThread(chapterId + 1);
        unlockAndBlock.unlock();
    }

}
